<?php
/**
* object-oriented file system manipulation as nodes.
* This file belongs to a webappkit package (kit)
* @link http://webappkit.net
* @package pathnodes
* @subpackage ini
* @author J.Ducastel <nospam0@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* registers as pathnode descendant
*/
pathnode::heritage('inifilenode',20);
/**
* INI file node manipulation.
* @link http://en.wikipedia.org/wiki/INI_file
* @todo comment preservation and handling
*/
class iniFileNode extends textFileNode {

    /**
    * sets some data within the cache
    * @return bool
    * @todo synchronization check
    * @todo check keys formats
    */
    function setData($section, $param=null, $value=null) {
        // reading if !sync
        if (!$this->_sync) $this->fileToCache();
        switch (true) {
            case $section and $param:
                $this->sections[$section][$param]=$value;
                $this->_sync=false;
                return true;
            case $section:
                // setting a whole section at once
                $this->sections[$section]=$value;
                $this->_sync=false;
                return true;
            case !$section and $param:
                // setting a root param
                $this->params[$param]=$value;
                $this->_sync=false;
            default:
                return false;
        }
    }
    /**
    * return partial or complete data.
    * @param string $section
    * @param string $param
    * @todo sync check 
    * @return mixed targeted data
    */
    function getData($section=null,$param=null) {
        // reading if !sync
        if (!$this->_sync) $this->fileToCache();
        switch (true) {
            case $section and $param:
                if (!isset($this->sections[$section][$param]))
                    return null;
                return $this->sections[$section][$param];
            case $section:
                // getting a whole section at once
                if (!isset($this->sections[$section]))
                    return null;
                return $this->sections[$section];
            case $param:
                // getting a root param
                if (!isset($this->params[$param]))
                    return null;
                return $this->params[$param];
            default:
                return array_merge($this->params,$this->sections);
                return false;
        }
    }
    
    /**
    * 
    * @param
    * @access public
    * @return 
    * /
    function getComment($section=null,$param=null) {
        
    }/**/
    
    /**
    * 
    * @param
    * @access public
    * @return 
    * /
    function setComment($section=null,$param=null,$comment) {
        
    }/**/
    
    /**
    * loads file's content
    * @access public
    * @return bool
    */
    function load() {
        return $this->fileToCache();
    }/**/
    
    /**
    * saves content to file
    * @access public
    * @return bool
    */
    function save() {
        return $this->cacheToFile();
    }/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
    /**
    * @var type desc
    */
    //var $type='inifile';
    //var $content_cache; // content cache
    //var $comments_cache; // content comments cache
    /**
    * @var bool is content synchornized with file ?
    */
    var $_sync=false;
    /**
    * @var array root vars data
    */
    var $params=array();
    /**
    * @var array section vars data
    */
    var $sections=array();
    /**
    * @var array root/file comment lines
    */
    var $file_comment_lines=array();
    /**
    * @var array root vars comment lines
    */
    var $params_comment_lines=array();
    /**
    * @var array section comments
    */
    var $sections_comment_lines=array();
    /**
    * @var array section vars comments
    */
    var $sections_params_comment_lines=array(); //
    /**
    * @var string regular expression for keys check
    */
    var $_ereg_key='[^ ]+';
    // var $_ereg_param='';
/*------------------------------------------------------------------------------
                                                               protected methods
------------------------------------------------------------------------------*/
    /**
    * detects if path applies to self class.
    * subclasses HAVE to implement this method
    * @param string $path
    * @access public
    * @return bool
    */
    function selfDetector($path) {
        return ereg('\.ini$',$path)/* and is_file($path)*/;
    }/**/

    /**
    * reads file's content into cache.
    * @access protected
    * @return bool
    */
    function fileToCache() {
        if (!$this->exists()) return false;
        // reading file
        $lines=file($this->path);
        // parsing file
        $section=null; $param=null;
        foreach ($lines as $line) {
            if (!$line or trim($line)=='') continue;
            if (ereg('^ *;(.*)',$line,$found)) {
                // comment line
                $comment=trim($found[1]);
                $this->addCommentLineFor($comment,$section,$param);
                continue;
            } else if ($name=$this->parseSectionLine($line)) {
                // section start
                $section=$name; // echo "<br /> section $section";
                $this->sections[$section]=array();
                $param=null;
                //continue
            } else if ($data=$this->parseParamLine($line)) {
                $param=$data['key'];
                // param line
                if (!$section) {
                    // root param
                    $this->params[$param]=$data['value'];
                } else {
                    // param within section
                    $this->sections[$section][$param]=$data['value'];
                }
            }
        }
        $this->_sync=true;
        return true;
    }/**/
    
    /**
    * converts cache to a string, and writes it in the ini file.
    * @access protected
    * @return bool
    */
    function cacheToFile() {
        $str='';
        // file-level comments
        //$str.=$this->makeCommentLines();
        // cycling root params
        foreach ($this->params as $key => $value) {
            // param line
            $str.=$this->makeParamLine($key,$value);
            // param comments
            //$str.=$this->makeCommentLines(null,$key);
        }
        // cycling sections
        foreach ($this->sections as $section => $content) {
            // section start
            $str.=$this->newlinestring()."[$section]";
            // section-level comments
            //$str.=$this->makeCommentLines($section);
            // sections params
            if (is_array($this->sections[$section])) {
                foreach ($this->sections[$section] as $key => $value) {
                    // param line
                    $str.=$this->makeParamLine($key,$value);
                    // param comments
                    //$str.=$this->makeCommentLines($section,$key);
                }
            }
        }
        // echo '<pre>'.$str.'</pre>';
        // writing data
        $this->erase();
        $ok=$this->write($str,0);
        if ($ok) $this->_sync=true;
        return $ok;
    }/**/
    /**
    * reads section line. return the section name, or false
    * @param string $line
    * @access private
    * @return string or false
    */
    function parseSectionLine($line) {
        if (!ereg('\[('.$this->_ereg_key.')\]',$line,$found))
            return false;
        return $found[1];
    }/**/
    
    /**
    * parses param line. return 
    * @param string $line
    * @access private
    * @return array 'key'=>$key,'value'=>$value
    */
    function parseParamLine($line) {
        $line=trim($line);
        // echo "<p>parsing $line</p>";
        if (!ereg('('.$this->_ereg_key.') *= *(.*)$',$line,$found)) {
            trigger_error("wrong ini param line $line");
            return false;
        }
        //$d=&wak::getservice('wakdebug');
        //echo $d->vardump($found);
        $key=$found[1];
        $value=$found[2]; //echo $d->vardump($value);
        switch (true) {
            case strtolower($value)=='true':
                $value=true;
                break;
            case strtolower($value)=='false':
                $value=false;
                break;
            case strtolower($value)=='null':
                $value=null;
                break;
            case ereg('^[0-9]+$',$value): // integer value
                $value=intval($value);
                break;
            //case ereg
            case ereg('^"(.*)"$',$value,$found): // simple quoted string value
                $value=strval($found[1]);
                break;
            default:
                $value=strval($value);
                break;
        }
        return array('key'=>$key,'value'=>$value);
    }/**/
    
    /**
    * creates a param line
    * @access private
    * @param string $key
    * @param mixed $value
    * @return string
    */
    function makeParamLine($key, $value) {
        $str=$this->newlinestring().$key.' = ';
        switch (true) {
            case is_bool($value) and $value:
                $str.='true';
                break;
            case is_bool($value) and !$value:
                $str.='false';
                break;
            case is_null($value):
                $str.='null';
                break;
            case is_numeric($value):
                $str.=strval($value);
                break;
            default:
                $str.='"'.strval($value).'"';
        }
        return $str;
    }
    
    /**
    * return comment lines for file, section, or param
    * @access private
    * @param string $section section's name
    * @param string $param param's key
    * @return string
    */
    function makeCommentLines($section=null, $param=null) {
        $str='';
        switch (true) {
            case $section and $param and isset($this->sections_params_comment_lines[$section][$param]):
                $array=$this->sections_params_comment_lines[$section][$param];
                break;
            case $param and !$section and isset($this->params_comment_lines[$param]):
                $array=$this->params_comment_lines[$param];
                break;
            case $section and !$param and isset($this->sections_comment_lines[$section]):
                $array=$this->sections_comment_lines[$section];
                break;
            case !$section and !$param:
                $array=$this->file_comment_lines;
                break;
            default:
                $array=array();
        }
        //$d=wak::getservice('wakdebug');
        //if ($d->haswrongtype($array,'array')) return $str;
        foreach ($array as $line)
            $str.=$this->newlinestring().'; '.$line;
        return $str;
    }
    
    /**
    * adds a comment line for file, section, and/or param
    * @return bool
    */
    function addCommentLineFor($comment, $section=null, $param=null) {
        if (!$section and !$param) {
            // file-level
            $this->file_comment_lines[]=$comment;
        } else if (!$section) {
            // param-level
            $this->params_comment_lines[$param][]=$comment;
        } else if (!$param) {
            // section-level
            $this->sections_comment_lines[$section][]=$comment;
        } else {
            // section+param level
            $this->sections_params_comment_lines[$section][$param][]=$comment;
        }
        return true;
    }/**/
    
}
?>
